/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionDefaults;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;

/**
 * A bean definition scanner that detects bean candidates on the classpath,
 * registering corresponding bean definitions with a given registry ({@code BeanFactory}
 * or {@code ApplicationContext}).
 *
 * <p>Candidate classes are detected through configurable type filters. The
 * default filters include classes that are annotated with Spring's
 * {@link org.springframework.stereotype.Component @Component},
 * {@link org.springframework.stereotype.Repository @Repository},
 * {@link org.springframework.stereotype.Service @Service}, or
 * {@link org.springframework.stereotype.Controller @Controller} stereotype.
 *
 * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
 * JSR-330's {@link javax.inject.Named} annotations, if available.
 *
 *
 * 类路径的扫描器
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Chris Beams
 * @see AnnotationConfigApplicationContext#scan
 * @see org.springframework.stereotype.Component
 * @see org.springframework.stereotype.Repository
 * @see org.springframework.stereotype.Service
 * @see org.springframework.stereotype.Controller
 * @since 2.5
 */
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

    private final BeanDefinitionRegistry registry;

    private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();

    @Nullable
    private String[] autowireCandidatePatterns;

    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

    private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();

    private boolean includeAnnotationConfig = true;


    /**
     * Create a new {@code ClassPathBeanDefinitionScanner} for the given bean factory.
     *
     * @param registry the {@code BeanFactory} to load bean definitions into, in the form
     *                 of a {@code BeanDefinitionRegistry}
     */
    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        this(registry, true);
    }

    /**
     * Create a new {@code ClassPathBeanDefinitionScanner} for the given bean factory.
     * <p>If the passed-in bean factory does not only implement the
     * {@code BeanDefinitionRegistry} interface but also the {@code ResourceLoader}
     * interface, it will be used as default {@code ResourceLoader} as well. This will
     * usually be the case for {@link org.springframework.context.ApplicationContext}
     * implementations.
     * <p>If given a plain {@code BeanDefinitionRegistry}, the default {@code ResourceLoader}
     * will be a {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
     * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
     * environment will be used by this reader.  Otherwise, the reader will initialize and
     * use a {@link org.springframework.core.env.StandardEnvironment}. All
     * {@code ApplicationContext} implementations are {@code EnvironmentCapable}, while
     * normal {@code BeanFactory} implementations are not.
     *
     * @param registry          the {@code BeanFactory} to load bean definitions into, in the form
     *                          of a {@code BeanDefinitionRegistry}
     * @param useDefaultFilters whether to include the default filters for the
     *                          {@link org.springframework.stereotype.Component @Component},
     *                          {@link org.springframework.stereotype.Repository @Repository},
     *                          {@link org.springframework.stereotype.Service @Service}, and
     *                          {@link org.springframework.stereotype.Controller @Controller} stereotype annotations
     * @see #setResourceLoader
     * @see #setEnvironment
     */
    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
    }

    /**
     * Create a new {@code ClassPathBeanDefinitionScanner} for the given bean factory and
     * using the given {@link Environment} when evaluating bean definition profile metadata.
     * <p>If the passed-in bean factory does not only implement the {@code
     * BeanDefinitionRegistry} interface but also the {@link ResourceLoader} interface, it
     * will be used as default {@code ResourceLoader} as well. This will usually be the
     * case for {@link org.springframework.context.ApplicationContext} implementations.
     * <p>If given a plain {@code BeanDefinitionRegistry}, the default {@code ResourceLoader}
     * will be a {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
     *
     * @param registry          the {@code BeanFactory} to load bean definitions into, in the form
     *                          of a {@code BeanDefinitionRegistry}
     * @param useDefaultFilters whether to include the default filters for the
     *                          {@link org.springframework.stereotype.Component @Component},
     *                          {@link org.springframework.stereotype.Repository @Repository},
     *                          {@link org.springframework.stereotype.Service @Service}, and
     *                          {@link org.springframework.stereotype.Controller @Controller} stereotype annotations
     * @param environment       the Spring {@link Environment} to use when evaluating bean
     *                          definition profile metadata
     * @see #setResourceLoader
     * @since 3.1
     */
    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
                                          Environment environment) {

        this(registry, useDefaultFilters, environment,
                (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
    }

    /**
     * Create a new {@code ClassPathBeanDefinitionScanner} for the given bean factory and
     * using the given {@link Environment} when evaluating bean definition profile metadata.
     *
     * @param registry          the {@code BeanFactory} to load bean definitions into, in the form
     *                          of a {@code BeanDefinitionRegistry}
     * @param useDefaultFilters whether to include the default filters for the
     *                          {@link org.springframework.stereotype.Component @Component},
     *                          {@link org.springframework.stereotype.Repository @Repository},
     *                          {@link org.springframework.stereotype.Service @Service}, and
     *                          {@link org.springframework.stereotype.Controller @Controller} stereotype annotations
     * @param environment       the Spring {@link Environment} to use when evaluating bean
     *                          definition profile metadata
     * @param resourceLoader    the {@link ResourceLoader} to use
     * @since 4.3.6
     */
    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
                                          Environment environment, @Nullable ResourceLoader resourceLoader) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        if (useDefaultFilters) {
            // 将 Component 注解放入
            registerDefaultFilters();
        }
        setEnvironment(environment);
        setResourceLoader(resourceLoader);
    }

    /**
     * Get the Environment from the given registry if possible, otherwise return a new
     * StandardEnvironment.
     */
    private static Environment getOrCreateEnvironment(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        if (registry instanceof EnvironmentCapable) {
            return ((EnvironmentCapable) registry).getEnvironment();
        }
        return new StandardEnvironment();
    }

    /**
     * Return the BeanDefinitionRegistry that this scanner operates on.
     */
    @Override
    public final BeanDefinitionRegistry getRegistry() {
        return this.registry;
    }

    /**
     * Return the defaults to use for detected beans (never {@code null}).
     *
     * @since 4.1
     */
    public BeanDefinitionDefaults getBeanDefinitionDefaults() {
        return this.beanDefinitionDefaults;
    }

    /**
     * Set the defaults to use for detected beans.
     *
     * @see BeanDefinitionDefaults
     */
    public void setBeanDefinitionDefaults(@Nullable BeanDefinitionDefaults beanDefinitionDefaults) {
        this.beanDefinitionDefaults =
                (beanDefinitionDefaults != null ? beanDefinitionDefaults : new BeanDefinitionDefaults());
    }

    /**
     * Set the name-matching patterns for determining autowire candidates.
     *
     * @param autowireCandidatePatterns the patterns to match against
     */
    public void setAutowireCandidatePatterns(@Nullable String... autowireCandidatePatterns) {
        this.autowireCandidatePatterns = autowireCandidatePatterns;
    }

    /**
     * Set the BeanNameGenerator to use for detected bean classes.
     * <p>Default is a {@link AnnotationBeanNameGenerator}.
     */
    public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
        this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : new AnnotationBeanNameGenerator());
    }

    /**
     * Set the ScopeMetadataResolver to use for detected bean classes.
     * Note that this will override any custom "scopedProxyMode" setting.
     * <p>The default is an {@link AnnotationScopeMetadataResolver}.
     *
     * @see #setScopedProxyMode
     */
    public void setScopeMetadataResolver(@Nullable ScopeMetadataResolver scopeMetadataResolver) {
        this.scopeMetadataResolver =
                (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver());
    }

    /**
     * Specify the proxy behavior for non-singleton scoped beans.
     * Note that this will override any custom "scopeMetadataResolver" setting.
     * <p>The default is {@link ScopedProxyMode#NO}.
     *
     * @see #setScopeMetadataResolver
     */
    public void setScopedProxyMode(ScopedProxyMode scopedProxyMode) {
        this.scopeMetadataResolver = new AnnotationScopeMetadataResolver(scopedProxyMode);
    }

    /**
     * Specify whether to register annotation config post-processors.
     * <p>The default is to register the post-processors. Turn this off
     * to be able to ignore the annotations or to process them differently.
     */
    public void setIncludeAnnotationConfig(boolean includeAnnotationConfig) {
        this.includeAnnotationConfig = includeAnnotationConfig;
    }

    /**
     * Perform a scan within the specified base packages.
     *
     * @param basePackages the packages to check for annotated classes
     * @return number of beans registered
     */
    public int scan(String... basePackages) {

        // 获取bean数量
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        // 执行扫描
        doScan(basePackages);

        // Register annotation config processors, if necessary.
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }

    /**
     * Perform a scan within the specified base packages,
     * returning the registered bean definitions.
     * <p>This method does <i>not</i> register an annotation config processor
     * but rather leaves this up to the caller.
     *
     * @param basePackages the packages to check for annotated classes
     *                     包列表
     * @return set of beans registered if any for tooling registration purposes (never {@code null})
     */
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            // 寻找组件
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                // bean 作用域设置
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                // 设置生命周期
                candidate.setScope(scopeMetadata.getScopeName());
                // 创建beanName
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    // 设置默认属性 具体方法:org.springframework.beans.factory.support.AbstractBeanDefinition.applyDefaults
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    // 读取Lazy，Primary 等注解
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                // bean的重复检查
                if (checkCandidate(beanName, candidate)) {
                    // 创建 BeanDefinitionHolder
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    // 代理对象的处理
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    // 放入list中,最后返回用
                    beanDefinitions.add(definitionHolder);
                    // 注册bean
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

    /**
     * Apply further settings to the given bean definition,
     * beyond the contents retrieved from scanning the component class.
     *
     * 设置默认值
     * @param beanDefinition the scanned bean definition
     * @param beanName       the generated bean name for the given bean
     */
    protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
        beanDefinition.applyDefaults(this.beanDefinitionDefaults);
        if (this.autowireCandidatePatterns != null) {
            beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
        }
    }

    /**
     * Register the specified bean with the given registry.
     * <p>Can be overridden in subclasses, e.g. to adapt the registration
     * process or to register further bean definitions for each scanned bean.
     *
     * @param definitionHolder the bean definition plus bean name for the bean
     * @param registry         the BeanDefinitionRegistry to register the bean with
     */
    protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }

    /**
     * Check the given candidate's bean name, determining whether the corresponding
     * bean definition needs to be registered or conflicts with an existing definition.
     *
     * @param beanName       the suggested name for the bean
     * @param beanDefinition the corresponding bean definition
     * @return {@code true} if the bean can be registered as-is;
     * {@code false} if it should be skipped because there is an
     * existing, compatible bean definition for the specified name
     * @throws ConflictingBeanDefinitionException if an existing, incompatible
     *                                            bean definition has been found for the specified name
     */
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
        // 判断当前 beanName 是否在注册表中
        if (!this.registry.containsBeanDefinition(beanName)) {
            return true;
        }
        // 从注册表中获取
        BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
        // 当前的bean
        BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
        if (originatingDef != null) {
            existingDef = originatingDef;
        }
        if (isCompatible(beanDefinition, existingDef)) {
            return false;
        }
        throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
                "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
                "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
    }

    /**
     * Determine whether the given new bean definition is compatible with
     * the given existing bean definition.
     * <p>The default implementation considers them as compatible when the existing
     * bean definition comes from the same source or from a non-scanning source.
     *
     * @param newDefinition      the new bean definition, originated from scanning
     * @param existingDefinition the existing bean definition, potentially an
     *                           explicitly defined one or a previously generated one from scanning
     * @return whether the definitions are considered as compatible, with the
     * new definition to be skipped in favor of the existing definition
     */
    protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
        return (!(existingDefinition instanceof ScannedGenericBeanDefinition) ||  // explicitly registered overriding bean
                (newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) ||  // scanned same file twice
                newDefinition.equals(existingDefinition));  // scanned equivalent class twice
    }

}
